package com.like.bluetooth.sample.ble

import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableInt
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.like.bluetooth.ble.central.scan.executor.AbstractScanExecutor
import com.like.bluetooth.ble.central.scan.executor.ScanExecutorFactory
import com.like.bluetooth.exception.BluetoothException
import com.like.bluetooth.exception.BluetoothExceptionBusy
import com.like.bluetooth.exception.BluetoothExceptionCancelTimeout
import com.like.bluetooth.exception.BluetoothExceptionTimeout
import com.like.bluetooth.sample.R
import com.like.bluetooth.sample.databinding.FragmentBleScanBinding
import com.like.bluetooth.util.BluetoothPermissionUtils
import com.like.bluetooth.util.ScanResult
import com.like.common.util.Logger
import com.like.recyclerview.layoutmanager.WrapLinearLayoutManager
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.launch

/**
 * 扫描设备界面
 */
@SuppressLint("MissingPermission")
class BleScanFragment : Fragment() {
    private lateinit var mBinding: FragmentBleScanBinding
    private val mAdapter: BleScanAdapter by lazy { BleScanAdapter(requireActivity()) }
    private val scanExecutor: AbstractScanExecutor by lazy {
        ScanExecutorFactory.get(requireContext()).apply {
            onBleOn = {
                context?.let {
                    if (mBinding.tvScanStatus.text != "扫描未启动") {
                        val blueColor = ContextCompat.getColor(it, R.color.ble_text_blue)
                        mBinding.tvScanStatus.setTextColor(blueColor)
                        mBinding.tvScanStatus.text = "蓝牙已打开"
                    }
                }
            }
            onBleOff = {
                context?.let {
                    val redColor = ContextCompat.getColor(it, R.color.ble_text_red)
                    mBinding.tvScanStatus.setTextColor(redColor)
                    mBinding.tvScanStatus.text = "蓝牙未打开"
                }
            }
        }
    }

    companion object {
        fun newInstance(): BleScanFragment {
            return BleScanFragment()
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_ble_scan, container, true)
        mBinding.rv.layoutManager = WrapLinearLayoutManager(requireContext())
        mBinding.rv.adapter = mAdapter
        mBinding.btnStartScan.setOnClickListener {
            startScan()
        }
        mBinding.btnStopScan.setOnClickListener {
            stopScan()
        }
        return mBinding.root
    }

    private fun startScan() {
        lifecycleScope.launch {
            BluetoothPermissionUtils.requestScanEnvironment(requireActivity())
            var isFirstData = true
            mBinding.tvScanStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.ble_text_blue))
            val onCatch: suspend FlowCollector<ScanResult>.(Throwable) -> Unit = {
                Logger.e("BleScanFragment", "scan error $it")
                when (it) {
                    is BluetoothExceptionCancelTimeout -> {
                        // 提前取消超时不做处理。因为这是调用 stopScan() 造成的，使用者可以直接在 stopScan() 方法结束后处理 UI 的显示，不需要此回调。
                    }

                    is BluetoothExceptionBusy -> {
                        mBinding.tvScanStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.ble_text_blue))
                        mBinding.tvScanStatus.text = "扫描中……"
                        Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show()
                    }

                    is BluetoothExceptionTimeout -> {
                        mBinding.tvScanStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.ble_text_blue))
                        mBinding.tvScanStatus.text = "扫描完成"
                    }

                    else -> {
                        mBinding.tvScanStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.ble_text_red))
                        mBinding.tvScanStatus.text = it.message
                        mAdapter.submitList(null)
                    }
                }
            }
            val onCollect: (ScanResult) -> Unit = {
                Logger.w("BleScanFragment", "scan result $it")
                if (isFirstData) {
                    isFirstData = false
                    mAdapter.submitList(null)
                }
                val name = it.device.name ?: "N/A"
                val address = it.device.address ?: ""
                val item: BleScanInfo? = mAdapter.currentList.firstOrNull { it?.address == address }
                if (item == null) {// 防止重复添加
                    val newItems = mAdapter.currentList.toMutableList()
                    newItems.add(BleScanInfo(name, address, ObservableInt(it.rssi), it.scanRecord))
                    mAdapter.submitList(newItems)
                } else {
                    item.updateRssi(it.rssi)
                }
            }
            mBinding.tvScanStatus.text = "扫描中……"
            scanExecutor.startScan().catch(onCatch).conflate()// 如果消费者还在处理，则丢弃新的数据。然后消费者处理完后，再去获取生产者中的最新数据来处理。
                .collect(onCollect)
        }
    }

    private fun stopScan() {
        val ctx = context ?: return
        try {
            scanExecutor.stopScan()
            mBinding.tvScanStatus.setTextColor(ContextCompat.getColor(ctx, R.color.ble_text_blue))
            mBinding.tvScanStatus.text = "扫描停止了"
        } catch (e: BluetoothException) {
            mBinding.tvScanStatus.setTextColor(ContextCompat.getColor(ctx, R.color.ble_text_red))
            mBinding.tvScanStatus.text = e.message
        }
    }

    override fun onDestroy() {
        scanExecutor.close()
        super.onDestroy()
    }

}
